package com.inspur.edp.commonmodel.engine.core.common;

import com.inspur.edp.bef.bizentity.common.BefDtBeanUtil;
import com.inspur.edp.cef.core.data.DataUtils;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.element.ElementDefaultVauleType;
import com.inspur.edp.cef.designtime.api.element.GspAssociation;
import com.inspur.edp.cef.designtime.api.element.GspElementObjectType;
import com.inspur.edp.cef.designtime.api.element.GspEnumValue;
import com.inspur.edp.cef.designtime.api.util.MetadataUtil;
import com.inspur.edp.cef.entity.accessor.base.IAccessor;
import com.inspur.edp.cef.entity.entity.ICefData;
import com.inspur.edp.cef.entity.entity.IEntityData;
import com.inspur.edp.cef.entity.entity.IValueObjData;
import com.inspur.edp.cef.entity.entity.dynamicProp.DynamicPropSetImpl;
import com.inspur.edp.cef.spi.common.UdtManagerUtil;
import com.inspur.edp.cef.spi.entity.info.propertyinfo.UdtPropertyInfo;
import com.inspur.edp.cef.spi.entity.resourceInfo.builinImpls.CefEntityResInfoImpl;
import com.inspur.edp.cef.spi.util.ExpressionUtil;
import com.inspur.edp.commonmodel.engine.api.data.AssociationInfo;
import com.inspur.edp.das.commonmodel.IGspCommonElement;
import com.inspur.edp.das.commonmodel.IGspCommonModel;
import com.inspur.edp.das.commonmodel.IGspCommonObject;
import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import com.inspur.edp.udt.designtime.api.entity.UnifiedDataTypeDef;
import com.inspur.edp.udt.entity.ISimpleUdtData;
import java.math.BigDecimal;
import java.sql.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.var;
import org.springframework.util.StringUtils;

public class CMUtil {

  public static final Predicate<IGspCommonField> ElementIsCustomizedPredicate = item -> isCustomized(
      item);

  public static final Predicate<IGspCommonObject> NodeIsCustomizedPredicate = item -> isCustomized(
      item);

  public static boolean isCustomized(IGspCommonField element) {
    return element.getCustomizationInfo() != null && element.getCustomizationInfo()
        .isCustomized();
  }

  public static boolean isCustomized(IGspCommonObject node) {
    return node.getCustomizationInfo() != null && node.getCustomizationInfo().isCustomized();
  }

  public static IGspCommonObject findNode(IGspCommonModel be, String childCode) {
    Objects.requireNonNull(be, "be");
    Objects.requireNonNull(childCode, "childCode");

    IGspCommonObject node = (IGspCommonObject) be.getAllObjectList().stream()
        .filter(item -> item.getCode().equalsIgnoreCase(childCode))
        .findFirst()
        .orElse(null);
    Objects.requireNonNull(node, "be" + be.getCode() + "上不存在子表" + childCode);
    return node;
  }

  public static IGspCommonObject findNode(IGspCommonObject node, String childCode) {
    Objects.requireNonNull(node, "node");
    Objects.requireNonNull(childCode, "childCode");

    IGspCommonObject childNode =
        (IGspCommonObject)
            node.getContainChildObjects().stream()
                .filter(item -> item.getCode().equalsIgnoreCase(childCode))
                .findFirst()
                .orElse(null);
    Objects.requireNonNull(childNode, "子表" + node.getCode() + "上不存在子表" + childCode);
    return childNode;
  }

  public static IGspCommonElement checkElementExists(IGspCommonObject node, String labelId,
      Predicate<IGspCommonField> elementPredicate) {
    Objects.requireNonNull(labelId, "labelId");

    IGspCommonField element = findElement(node, labelId);
    if (element == null || (elementPredicate != null && !elementPredicate.test(element))) {
      throwElementNotFound(node, labelId);
    }
    return (IGspCommonElement) element;
  }

  public static boolean isElementExist(IGspCommonObject node, String labelId,
      Predicate<IGspCommonField> elementPredicate) {
    IGspCommonField element = findElement(node, labelId);
    return element != null && (elementPredicate == null || elementPredicate.test(element));
  }

  private static IGspCommonElement findElement(IGspCommonObject node, String labelId) {
    return (IGspCommonElement) node.getContainElements().getByLabelId(labelId);
  }

  public static IGspCommonElement checkElementValue(IGspCommonObject node, String labelId,
      Object value, Predicate<IGspCommonField> elementPredicate) {
    IGspCommonElement element = checkElementExists(node, labelId, elementPredicate);

    if (element.getIsUdt()) {
      if (value != null && !(value instanceof IValueObjData)) {
        throwInvalidPropertyValue(node, element, value);
      }
    } else if (element.getObjectType() == GspElementObjectType.Association) {
      if (value != null) {
        if (value instanceof AssociationInfo) {
          GspAssociation association = element.getChildAssociations().get(0);
          ((AssociationInfo) value).setAssociation(association);
          for (Map.Entry<String, Object> key : ((AssociationInfo) value).getValues()
              .entrySet()) {
            checkRefElementExists(association, key.getKey());
            //TODO:check key.getValue()
          }
        } else {
          throwInvalidPropertyValue(node, element, value);
        }
      }
    }
    return element;
    //TODO: check enum/normal
  }

  private static void throwInvalidPropertyValue(IGspCommonObject node, IGspCommonElement element,
      Object value) {
    throw new RuntimeException(
        (value != null ? value.getClass() : "null") + " is not valid for" + node
            .getCode() + "." + element.getLabelID());
  }

  public static IGspCommonObject checkNodeExists(IGspCommonObject node, String childCode,
      Predicate<IGspCommonObject> predicate) {
    Objects.requireNonNull(childCode, "childCode");

    IGspCommonObject element = null;
    for(IGspCommonObject child : node.getContainChildObjects()){
      if(child.getCode().equalsIgnoreCase(childCode)) {
        element = child;
        break;
      }
    }
    if (element == null || (predicate != null && !predicate.test(element))) {
      throwNodeNotFound(node, childCode);
    }
    return element;
  }

  public static IGspCommonElement checkRefElementExists(
      GspAssociation association, String labelId) {
    if (association.getBelongElement().getLabelID().equalsIgnoreCase(labelId)) {
      return (IGspCommonElement) association.getBelongElement();
    }
    IGspCommonElement field =
        (IGspCommonElement)
            association.getRefElementCollection().stream()
                .filter(item -> item.getLabelID().equalsIgnoreCase(labelId))
                .findFirst()
                .orElse(null);
    if (field == null) {
      throwRefElementNotFound(association, labelId);
    }
    return field;
  }

  public static <TKey, TValue> HashMap<TKey, TValue> cloneForValues(HashMap<TKey, TValue> source)
      throws CloneNotSupportedException {
    if (source == null) {
      return null;
    }
    HashMap<TKey, TValue> result = new HashMap<>(source.size(), 1);
    for (Map.Entry<TKey, TValue> pair : source.entrySet()) {
      Object value = null;
      if (pair.getValue() == null) {
        value = null;
      } else if (pair.getValue() instanceof AssociationInfo) {
        value = ((AssociationInfo) pair.getValue()).clone();
      } else if (pair.getValue() instanceof ICefData) {
        value = ((ICefData) pair.getValue()).copySelf();
      } else {
        value = pair.getValue();
      }
      result.put(pair.getKey(), (TValue) value);
    }
    return result;
  }

  public static Stream<IGspCommonField> streamElements(IGspCommonObject node,
      Predicate<IGspCommonField> predicate) {
    Stream<IGspCommonField> rez = node.getContainElements().stream();
    if (predicate != null) {
      rez = rez.filter(predicate);
    }
    return rez;
  }

  public static String getUdtConfigId(String udtId, String udtPackgeName) {
    GspMetadata udtMetadata;
    try{
      udtMetadata = MetadataUtil.getCustomRTMetadata(udtId);
    } catch(Exception e) {
      throw new RuntimeException("获取Udt元数据失败" + udtId, e);
    }
    var udtContent = (UnifiedDataTypeDef) (udtMetadata != null ? udtMetadata.getContent() : null);
    if (udtContent == null) {
      throw new RuntimeException("udt元数据不存在" + udtId);
    }
    return udtContent.getUdtType();
  }

  public static void initValue(IGspCommonObject node, IEntityData data,
      Predicate<IGspCommonField> predicate) {
    initValue(node,data,predicate,null);
  }

  public static void initValue(IGspCommonObject node, IEntityData data,
      Predicate<IGspCommonField> predicate, CefEntityResInfoImpl resInfo) {
    for (IGspCommonField field : node.getContainElements()) {
      if (predicate != null && !predicate.test(field)) {
        continue;
      }

      if (node.getIDElement() == field) {
        continue;
      }

      if (field.getIsUdt()) {
        String udtConfigId = getUdtConfigId(field, resInfo);
        data.setValue(field.getLabelID(),
            UdtManagerUtil.getUdtFactory().createManager(udtConfigId).createDataType());
        continue;
      }

      switch (field.getObjectType()) {
        case None://普通字段不处理
          break;
        case Enum:
          GspEnumValue defaultValue = field.getContainEnumValues().stream()
              .filter(item -> item.getIsDefaultEnum()).findFirst()
              .orElse(field.getContainEnumValues().get(0));
          data.setValue(field.getLabelID(), defaultValue.getValue());
          break;
        case DynamicProp:
          data.setValue(field.getLabelID(), new DynamicPropSetImpl());
          break;
        case Association:
          if (field.getObjectType() == GspElementObjectType.Association
              && field.getChildAssociations() != null &&
              !field.getChildAssociations().isEmpty()) {
            data.setValue(field.getLabelID(),
                new AssociationInfo(field.getChildAssociations().get(0)));
          }
          break;
      }
    }
  }

  @Deprecated
  public static void accInitNestedValue(IAccessor acc, IGspCommonObject node,
      Map<String, Object> values, Predicate<IGspCommonField> predicate) {
    accInitNestedValue(acc, node, values, predicate, null);
  }

  public static void accInitNestedValue(IAccessor acc, IGspCommonObject node,
      Map<String, Object> values, Predicate<IGspCommonField> predicate, CefEntityResInfoImpl resInfo) {
    CMUtil.streamElements(node, predicate)
        .forEach(item -> {
          if (item.getIsUdt()) {
            String configId = getUdtConfigId(item, resInfo);
            values.put(item.getLabelID(),
                DataUtils.createAccNested(acc, configId, item.getLabelID(), acc.getIsReadonly()));
          } else if (item.getObjectType() == GspElementObjectType.DynamicProp) {
            values.put(item.getLabelID(),
                DataUtils.createDynPropSetAcc(acc, item.getLabelID(), acc.getIsReadonly()));
          }
        });
  }

  public static String getUdtConfigId(IGspCommonField field, CefEntityResInfoImpl resInfo) {
    if(resInfo!=null && resInfo.getEntityTypeInfo().getPropertyInfo(field.getLabelID())!=null)
      return ((UdtPropertyInfo)resInfo.getEntityTypeInfo().getPropertyInfo(field.getLabelID()).getObjectInfo()).getUdtConfigId();
    return getUdtConfigId(field.getUdtID(), field.getUdtPkgName());
  }

  public static void assignDefaultValue(IGspCommonObject node, IEntityData data) {
    for (IGspCommonField field : node.getContainElements()) {
      try {
        if (field.getIsUdt() || StringUtils.isEmpty(field.getDefaultValue())) {
          continue;
        }

        switch (field.getObjectType()) {
          case Enum:
//          field.getContainEnumValues().stream().findAny(item -> item.).con
            data.setValue(field.getLabelID(), field.getDefaultValue());
            break;
          case None:
            assignDefaultValue_NoneObjType(field, data);
            break;
          case Association:
            throw new RuntimeException(
                "不支持将默认值 [" + field.getDefaultValue() + "] 设置在关联字段:" + field.getLabelID());
        }
      // TODO: 动态属性初始值
      } catch(Exception e) {
        throw new RuntimeException(String
            .format("ID编号名称分别为[%s][%s][%s]的模型的节点[%s]的字段[%s]上设置的默认值[%s]格式不正确, 请联系元数据开发人员修改.",
                node.getBelongModel().getID(), node.getBelongModel().getCode(),
                node.getBelongModel().getName(), node.getCode(), field.getCode(),
                field.getDefaultValue()));
      }
    }
  }

  public static void assignDefaultValue_NoneObjType(IGspCommonField field, IEntityData data) {
    if (field.getMDataType() == null) {
      return;
    }
    Object value = null;
    Object tempValue = null;
    if(field.getDefaultValueType() == ElementDefaultVauleType.Expression){
      tempValue = ExpressionUtil.getExpressionValue(field.getDefaultValue());
      tempValue = tempValue == null ? "" : tempValue;

      switch (field.getMDataType()) {
        case String:
        case Text:
          value = tempValue.toString();
          break;
        case Integer:
          value = Integer.valueOf(tempValue.toString());
          break;
        case Decimal:
          value = new BigDecimal(tempValue.toString());
          break;
        case Boolean:
          value = Boolean.valueOf(tempValue.toString());
          break;
        case Date:
        case DateTime:
          value = tempValue;
          break;
        case Binary:
          throw new RuntimeException(
                  "不支持将默认值 [" + field.getDefaultValue() + "] 设置在二进制字段:" + field.getLabelID());
      }
      data.setValue(field.getLabelID(), value);
    }
    else {
      switch (field.getMDataType()) {
        case String:
        case Text:
          value = field.getDefaultValue();
          break;
        case Integer:
          value = Integer.valueOf(field.getDefaultValue());
          break;
        case Decimal:
          value = new BigDecimal(field.getDefaultValue());
          break;
        case Boolean:
          value = Boolean.valueOf(field.getDefaultValue());
          break;
        case Date:
        case DateTime:
          value = Date.valueOf(field.getDefaultValue());
          break;
        case Binary:
          throw new RuntimeException(
                  "不支持将默认值 [" + field.getDefaultValue() + "] 设置在二进制字段:" + field.getLabelID());
      }
      data.setValue(field.getLabelID(), value);
    }

  }

  public static void throwNodeNotFound(IGspCommonObject node, String childCode) {
    throw new RuntimeException("找不到节点" + node.getCode() + "." + childCode);
  }

  public static void throwElementNotFound(IGspCommonObject node, String labelId) {
    throw new RuntimeException("找不到字段" + node.getCode() + "." + labelId);
  }

  public static void throwMgrActionNotFound(IGspCommonModel be, String actionCode) {
    throw new RuntimeException("找不到自定义动作" + be.getCode() + "." + actionCode);
  }

  public static void throwRefElementNotFound(GspAssociation association, String labelId) {
    throw new RuntimeException(
        "关联带出字段不存在" + association.getBelongElement().getLabelID() + "." + labelId);
  }

  public static String convert2String(Object value){
    if(value == null){
      return null;
    }
    if(value instanceof String) {
      return (String) value;
    }
    if(value instanceof AssociationInfo){
      return ((AssociationInfo)value).getValue();
    }
    if(value instanceof ISimpleUdtData){
      return (String)((ISimpleUdtData)value).getValue();
    }
    throw new RuntimeException();
  }

  public static List<String> buildNodePropertyNames(IGspCommonObject node, Predicate<IGspCommonField> predicate) {
    return streamElements(node, predicate).map(item -> item.getLabelID()).collect(Collectors.toList());
  }
}
